//
// strtol.c
//
// String to number conversion
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "ZHIA IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <os.h>
#include <ctype.h>
#include <stdint.h>

#define FL_UNSIGNED   1
#define FL_NEG        2
#define FL_OVERFLOW   4
#define FL_READDIGIT  8

static unsigned long strtoxl(const char *nptr, char **endptr, int ibase, int flags) {
  const unsigned char *p;
  char c;
  unsigned long number;
  unsigned digval;
  unsigned long maxval;

  p = (const unsigned char *) nptr;
  number = 0;

  c = *p++;
  while (isspace(c)) c = *p++;

  if (c == '-') {
    flags |= FL_NEG;
    c = *p++;
  } else if (c == '+') {
    c = *p++;
  }

  if (ibase < 0 || ibase == 1 || ibase > 36) {
    if (endptr) *endptr = (char *) nptr;
    return 0L;
  } else if (ibase == 0) {
    if (c != '0') {
      ibase = 10;
    } else if (*p == 'x' || *p == 'X') {
      ibase = 16;
    } else {
      ibase = 8;
    }
  }

  if (ibase == 16) {
    if (c == '0' && (*p == 'x' || *p == 'X')) {
      ++p;
      c = *p++;
    }
  }

  maxval = UINT32_MAX / ibase;

  for (;;) {
    if (isdigit(c)) {
      digval = c - '0';
    } else if (isalpha(c)) {
      digval = toupper(c) - 'A' + 10;
    } else {
      break;
    }

    if (digval >= (unsigned) ibase) break;

    flags |= FL_READDIGIT;

    if (number < maxval || (number == maxval && (unsigned long) digval <= UINT32_MAX % ibase)) {
      number = number * ibase + digval;
    } else {
      flags |= FL_OVERFLOW;
    }

    c = *p++;
  }

  --p;

  if (!(flags & FL_READDIGIT)) {
    if (endptr) p = nptr;
    number = 0;
  } else if ((flags & FL_OVERFLOW) || (!(flags & FL_UNSIGNED) && (((flags & FL_NEG) && (number < INT32_MIN)) || (!(flags & FL_NEG) && (number > INT32_MAX))))) {
#ifndef KERNEL
    errno = ERANGE;
#endif

    if (flags & FL_UNSIGNED) {
      number = UINT32_MAX;
    } else if (flags & FL_NEG) {
      number = INT32_MIN;
    } else {
      number = INT32_MAX;
    }
  }

  if (endptr) *endptr = (char *) p;

  if (flags & FL_NEG) number = (unsigned long) (-(long) number);

  return number;
}

long strtol(const char *nptr, char **endptr, int ibase) {
  return (long) strtoxl(nptr, endptr, ibase, 0);
}

unsigned long strtoul(const char *nptr, char **endptr, int ibase) {
  return strtoxl(nptr, endptr, ibase, FL_UNSIGNED);
}

long atol(const char *nptr) {
  int c;
  int sign;
  long total;
  const unsigned char *p = (const unsigned char *) nptr;

  while (isspace(*p)) ++p;

  c = *p++;
  sign = c;
  if (c == '-' || c == '+') c = *p++;

  total = 0;
  while (isdigit(c)) {
    total = 10 * total + (c - '0');
    c = *p++;
  }

  if (sign == '-') {
    return -total;
  } else {
    return total;
  }
}

int atoi(const char *nptr) {
  return (int) atol(nptr);
}

#ifndef KERNEL
static unsigned __int64 strtoxll(const char *nptr, char **endptr, int ibase, int flags) {
  const unsigned char *p = (const unsigned char *) nptr;
  unsigned __int64 number = 0;
  unsigned __int64 digval;
  unsigned __int64 maxval;
  int c;

  c = *p++;
  while (isspace(c)) c = *p++;

  if (c == '-') {
    flags |= FL_NEG;
    c = *p++;
  } else if (c == '+') {
    c = *p++;
  }

  if (ibase < 0 || ibase == 1 || ibase > 36) {
    if (endptr) *endptr = (char *) nptr;
    return 0;
  } else if (ibase == 0) {
    if (c != '0') {
      ibase = 10;
    } else if (*p == 'x' || *p == 'X') {
      ibase = 16;
    } else {
      ibase = 8;
    }
  }

  if (ibase == 16) {
    if (c == '0' && (*p == 'x' || *p == 'X')) {
      ++p;
      c = *p++;
    }
  }

  maxval = INT64_MAX / ibase;

  for (;;) {
    if (isdigit(c)) {
      digval = c - '0';
    } else if (isalpha(c)) {
      digval = toupper(c) - 'A' + 10;
    } else {
      break;
    }

    if (digval >= (unsigned __int64) ibase) break;

    flags |= FL_READDIGIT;

    if (number < maxval || (number == maxval && digval <= (unsigned __int64) (INT64_MAX % ibase))) {
      number = number * ibase + digval;
    } else {
      flags |= FL_OVERFLOW;
    }

    c = *p++;
  }

  --p;

  if (!(flags & FL_READDIGIT)) {
    if (endptr) p = nptr;
    number = 0;
  } else if ((flags & FL_OVERFLOW) || (!(flags & FL_UNSIGNED) && (((flags & FL_NEG) && (number < INT64_MIN)) || (!(flags & FL_NEG) && (number > INT64_MAX))))) {
    errno = ERANGE;

    if (flags & FL_UNSIGNED) {
      number = UINT64_MAX;
    } else if (flags & FL_NEG) {
      number = INT64_MIN;
    } else {
      number = INT64_MAX;
    }
  }

  if (endptr) *endptr = (char *) p;
  if (flags & FL_NEG) number = (unsigned __int64) (-(__int64) number);

  return number;
}

__int64 strtoll(const char *nptr, char **endptr, int ibase) {
  return (__int64) strtoxll(nptr, endptr, ibase, 0);
}

unsigned __int64 strtoull(const char *nptr, char **endptr, int ibase) {
  return strtoxll(nptr, endptr, ibase, FL_UNSIGNED);
}

intmax_t strtoimax(const char *nptr, char **endptr, int ibase) {
  return (intmax_t) strtoxll(nptr, endptr, ibase, 0);
}

uintmax_t strtoumax(const char *nptr, char **endptr, int ibase) {
  return strtoxll(nptr, endptr, ibase, FL_UNSIGNED);
}

#endif
